Системное программирование

Тема 5. Механизм сообщений в операционных системах

Системное программирование

План лекции

1. Парадигмы консольного и графического пользовательского оконного интерфейса

2. Класс окна в операционных системах, предопределенные классы

3. Получение и изменение данных окна и класса

4. Событийное управление приложениями

5. Основные элементы программ с оконным пользовательским интерфейсом

6. Понятие оконного сообщения

7. Источники сообщений

8. Очереди сообщений

9. Обработка сообщений мыши, клавиатуры

10. Графический интерфейс в Linux (X11, Wayland, GTK, Qt)

Механизм сообщений в операционных системах
Системное программирование

1. Парадигмы пользовательского интерфейса

1.1. Консольный интерфейс

Характеристики:

  • Текстовый ввод/вывод
  • Последовательное выполнение
  • Простота реализации
  • Низкие требования к ресурсам

Пример:

#include <stdio.h>

int main() {
    printf("Введите число: ");
    int num;
    scanf("%d", &num);
    printf("Вы ввели: %d\n", num);
    return 0;
}
Механизм сообщений в операционных системах
Системное программирование

1.2. Графический оконный интерфейс (GUI)

Характеристики:

  • Визуальные элементы управления
  • Событийно-управляемая модель
  • Многозадачность
  • Интуитивность использования

Архитектура GUI:

Механизм сообщений в операционных системах
Системное программирование

1.3. Сравнение парадигм

Характеристика Консоль GUI
Сложность разработки Низкая Высокая
Ресурсы Минимальные Значительные
Интерактивность Ограниченная Высокая
Пользовательский опыт Технический Интуитивный
Автоматизация Удобная Сложнее
Механизм сообщений в операционных системах
Системное программирование

2. Класс окна

2.1. Понятие класса окна

Класс окна — шаблон, определяющий характеристики группы окон.

Регистрация класса окна:

WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WindowProc;      // Оконная процедура
wc.hInstance = hInstance;
wc.lpszClassName = L"MyClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

RegisterClassEx(&wc);
Механизм сообщений в операционных системах
Системное программирование

2.2. Предопределенные классы Windows

Класс Назначение
BUTTON Кнопки
EDIT Поля ввода текста
STATIC Статический текст
LISTBOX Списки
COMBOBOX Комбинированные списки
SCROLLBAR Полосы прокрутки
RICHEDIT Расширенное редактирование

Создание элемента управления:

HWND hButton = CreateWindow(
    L"BUTTON",           // Класс
    L"Нажми меня",       // Текст
    WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
    10, 10, 100, 30,     // Позиция и размер
    hWndParent,          // Родительское окно
    (HMENU)ID_BUTTON,    // Идентификатор
    hInstance, NULL
);
Механизм сообщений в операционных системах
Системное программирование

3. Данные окна и класса

3.1. Получение и изменение данных

Дополнительная память окна:

// Выделение при регистрации класса
wc.cbWndExtra = sizeof(MyData);

// Установка данных
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)data);

// Получение данных
MyData* data = (MyData*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

Изменение стиля окна:

// Получение стиля
LONG style = GetWindowLong(hWnd, GWL_STYLE);

// Изменение стиля
style |= WS_BORDER;
SetWindowLong(hWnd, GWL_STYLE, style);

// Перерисовка
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
    SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
Механизм сообщений в операционных системах
Системное программирование

4. Событийное управление

4.1. Модель событийного программирования

Принцип:

  1. Приложение ожидает событий
  2. Событие поступает в очередь
  3. Диспетчер извлекает событие
  4. Вызывается обработчик
  5. Цикл повторяется

Механизм сообщений в операционных системах
Системное программирование

4.2. Цикл обработки сообщений

MSG msg;

// Стандартный цикл сообщений
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);  // Обработка клавиатуры
    DispatchMessage(&msg);   // Вызов оконной процедуры
}

// Альтернатива: PeekMessage для неблокирующего опроса
while (true) {
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        if (msg.message == WM_QUIT)
            break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    // Выполнение другой работы
}
Механизм сообщений в операционных системах
Системное программирование

5. Элементы оконного интерфейса

5.1. Структура оконного приложения

// 1. Регистрация класса окна
RegisterClassEx(&wc);

// 2. Создание окна
HWND hWnd = CreateWindowEx(
    0, L"MyClass", L"Заголовок",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    800, 600,
    NULL, NULL, hInstance, NULL
);

// 3. Отображение окна
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

// 4. Цикл сообщений
while (GetMessage(&msg, NULL, 0, 0)) { ... }

// 5. Завершение
return msg.wParam;
Механизм сообщений в операционных системах
Системное программирование

5.2. Оконная процедура

LRESULT CALLBACK WindowProc(
    HWND hWnd,      // Дескриптор окна
    UINT message,   // Код сообщения
    WPARAM wParam,  // Дополнительный параметр
    LPARAM lParam   // Дополнительный параметр
) {
    switch (message) {
        case WM_CREATE:
            // Инициализация
            return 0;
            
        case WM_PAINT:
            // Перерисовка
            return 0;
            
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
            
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
}
Механизм сообщений в операционных системах
Системное программирование

6. Оконные сообщения

6.1. Понятие сообщения

Сообщение — структура данных, описывающая событие.

typedef struct tagMSG {
    HWND   hwnd;       // Окно-получатель
    UINT   message;    // Код сообщения
    WPARAM wParam;     // Доп. параметр
    LPARAM lParam     // Доп. параметр
    DWORD  time;       // Время
    POINT  pt;         // Координаты курсора
} MSG;

Категории сообщений:

  • Системные (WM_CREATE, WM_DESTROY)
  • Ввода (WM_KEYDOWN, WM_MOUSEMOVE)
  • Рисования (WM_PAINT)
  • Управления (WM_COMMAND, WM_NOTIFY)
Механизм сообщений в операционных системах
Системное программирование

7. Источники сообщений

7.1. Откуда приходят сообщения?

Источники:

  1. Операционная система

    • Аппаратные события (клавиатура, мышь)
    • Системные события (таймер, окно)
  2. Приложение

    • PostMessage — асинхронная отправка
    • SendMessage — синхронная отправка
  3. Другие приложения

    • Межпроцессное взаимодействие
Механизм сообщений в операционных системах
Системное программирование

7.2. Отправка сообщений

// Асинхронная отправка (в очередь)
BOOL PostMessage(
    HWND hWnd,      // NULL — все окна
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

// Синхронная отправка (прямой вызов)
LRESULT SendMessage(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

// Пример
PostMessage(hWnd, WM_USER + 1, 0, 0);
SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)L"Новый текст");
Механизм сообщений в операционных системах
Системное программирование

8. Очереди сообщений

8.1. Типы очередей

Системная очередь:

  • Все события ввода
  • Одна на систему

Очередь потока:

  • Сообщения для окон потока
  • Одна на поток

Очередь окна:

  • Не существует как отдельная структура
  • Сообщения хранятся в очереди потока

Механизм сообщений в операционных системах
Системное программирование

8.2. Приоритеты сообщений

Функции получения:

// Блокирующее получение
GetMessage(&msg, NULL, 0, 0);

// Неблокирующая проверка
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);

// Фильтрация по диапазону
GetMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST);

Фильтры:

  • PM_REMOVE — удалить из очереди
  • PM_NOREMOVE — оставить в очереди
Механизм сообщений в операционных системах
Системное программирование

9. Обработка ввода

9.1. Сообщения клавиатуры

case WM_KEYDOWN:
    // Нажатие клавиши
    if (wParam == VK_ESCAPE)
        PostQuitMessage(0);
    break;

case WM_KEYUP:
    // Отпускание клавиши
    break;

case WM_CHAR:
    // Символ (с учётом раскладки)
    wchar_t ch = (wchar_t)wParam;
    break;

Виртуальные коды клавиш:

  • VK_RETURN — Enter
  • VK_ESCAPE — Escape
  • VK_SPACE — Пробел
  • VK_LEFT, VK_RIGHT — Стрелки
Механизм сообщений в операционных системах
Системное программирование

9.2. Сообщения мыши

case WM_LBUTTONDOWN:
    // Нажатие левой кнопки
    int x = GET_X_LPARAM(lParam);
    int y = GET_Y_LPARAM(lParam);
    break;

case WM_MOUSEMOVE:
    // Движение мыши
    break;

case WM_LBUTTONUP:
    // Отпускание левой кнопки
    break;

case WM_MOUSEWHEEL:
    // Колесо мыши
    int delta = GET_WHEEL_DELTA_WPARAM(wParam);
    break;
Механизм сообщений в операционных системах
Системное программирование

9.3. Обработка команд

case WM_COMMAND:
    switch (LOWORD(wParam)) {
        case ID_FILE_OPEN:
            // Открыть файл
            break;
        case ID_FILE_SAVE:
            // Сохранить файл
            break;
        case ID_BUTTON_CLICK:
            // Нажатие кнопки
            break;
    }
    break;
Механизм сообщений в операционных системах
Системное программирование

10. Графический интерфейс в Linux

10.1. Архитектура графической подсистемы Linux

Механизм сообщений в операционных системах
Системное программирование

10.2. X11 (X Window System)

X11 — сетевая оконная система с клиент-серверной архитектурой.

Компоненты:

Компонент Описание
X Server Управляет дисплеем, вводом
X Client Приложения
X Protocol Протокол коммуникации
Window Manager Управление окнами
Display Manager Экран входа
Механизм сообщений в операционных системах
Системное программирование

Xlib — низкоуровневый API:

#include <X11/Xlib.h>
#include <X11/Xutil.h>

int main() {
    Display* dpy = XOpenDisplay(NULL);
    if (!dpy) return 1;
    
    int screen = DefaultScreen(dpy);
    Window root = RootWindow(dpy, screen);
    
    // Создание окна
    Window win = XCreateSimpleWindow(
        dpy, root,
        10, 10, 400, 300,   // x, y, width, height
        1,                   // border width
        BlackPixel(dpy, screen),
        WhitePixel(dpy, screen)
    );
    
    XSelectInput(dpy, win, ExposureMask | KeyPressMask);
    XMapWindow(dpy, win);
    
    // Event loop
    XEvent e;
    while (1) {
        XNextEvent(dpy, &e);
        if (e.type == Expose) {
            // Отрисовка
        }
        if (e.type == KeyPress)
            break;
    }
    
    XCloseDisplay(dpy);
    return 0;
}
Механизм сообщений в операционных системах
Системное программирование

10.3. Wayland

Wayland — современный протокол для композитных оконных систем.

Преимущества перед X11:

Характеристика X11 Wayland
Архитектура Клиент-сервер Композитор + клиенты
Безопасность Любое окно может читать ввод Изоляция
Производительность Накладные расходы Прямой рендеринг
Сложность Большой код Минимализм
Сетевая прозрачность Да Через pipewire/waypipe
Механизм сообщений в операционных системах
Системное программирование

Wayland-клиент (libwayland):

#include <wayland-client.h>
#include <stdio.h>

static void registry_global(void* data, struct wl_registry* registry,
                            uint32_t name, const char* interface,
                            uint32_t version) {
    printf("Global: %s v%d\n", interface, version);
}

static const struct wl_registry_listener registry_listener = {
    .global = registry_global,
    .global_remove = NULL
};

int main() {
    struct wl_display* display = wl_display_connect(NULL);
    if (!display) return 1;
    
    struct wl_registry* registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);
    
    wl_display_roundtrip(display);
    wl_display_disconnect(display);
    return 0;
}
Механизм сообщений в операционных системах
Системное программирование

10.4. GTK (GIMP Toolkit)

GTK — кроссплатформенная библиотека для создания GUI.

#include <gtk/gtk.h>

static void on_activate(GtkApplication* app) {
    GtkWindow* window = GTK_WINDOW(gtk_application_window_new(app));
    gtk_window_set_title(window, "GTK Application");
    gtk_window_set_default_size(window, 400, 300);
    
    GtkWidget* button = gtk_button_new_with_label("Click Me!");
    gtk_window_set_child(window, button);
    
    g_signal_connect(button, "clicked", G_CALLBACK(gtk_window_close), window);
    
    gtk_window_present(window);
}

int main(int argc, char** argv) {
    GtkApplication* app = gtk_application_new("org.example.GtkApp",
                                               G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}
Механизм сообщений в операционных системах
Системное программирование

Компиляция:

gcc `pkg-config --cflags gtk4` -o app main.c `pkg-config --libs gtk4`
Механизм сообщений в операционных системах
Системное программирование

10.5. Qt Framework

Qt — кроссплатформенный фреймворк для C++.

#include <QApplication>
#include <QPushButton>
#include <QMainWindow>
#include <QMessageBox>

int main(int argc, char** argv) {
    QApplication app(argc, argv);
    
    QMainWindow window;
    window.setWindowTitle("Qt Application");
    window.resize(400, 300);
    
    QPushButton* button = new QPushButton("Click Me!", &window);
    button->setGeometry(100, 100, 200, 50);
    
    QObject::connect(button, &QPushButton::clicked, [&]() {
        QMessageBox::information(&window, "Message", "Button clicked!");
    });
    
    window.show();
    return app.exec();
}
Механизм сообщений в операционных системах
Системное программирование

Компиляция (qmake):

qmake -project
qmake
make

Компиляция (CMake):

find_package(Qt6 REQUIRED COMPONENTS Widgets)
target_link_libraries(myapp Qt6::Widgets)
Механизм сообщений в операционных системах
Системное программирование

10.6. Сравнение GUI-фреймворков

Фреймворк Язык Лицензия Особенности
GTK4 C LGPL Linux-first, GObject
Qt6 C++ LGPL/Commercial Rich ecosystem, QML
wxWidgets C++ wxWindows Native look
FLTK C++ LGPL Lightweight
Dear ImGui C++ MIT Immediate mode
SDL C zlib Games, multimedia
Механизм сообщений в операционных системах
Системное программирование

Резюме

Ключевые моменты лекции:

  1. GUI vs Console — разные парадигмы взаимодействия
  2. Класс окна — шаблон для создания окон (Windows)
  3. Событийное управление — основа GUI приложений
  4. Сообщения — механизм коммуникации
  5. Очереди сообщений — организация обработки
  6. Оконная процедура — обработчик событий
  7. Linux GUI — X11, Wayland, GTK, Qt
Механизм сообщений в операционных системах
Системное программирование

Вопросы для самопроверки

  1. Чем отличается консольный интерфейс от GUI?
  2. Что такое класс окна?
  3. Как работает цикл обработки сообщений?
  4. Какие источники сообщений существуют?
  5. В чём разница между PostMessage и SendMessage?
  6. Как обрабатывать ввод с клавиатуры и мыши?
  7. Чем отличается X11 от Wayland?
  8. Какие GUI-фреймворки существуют для Linux?
Механизм сообщений в операционных системах
Системное программирование

Практические задания

Самостоятельная работа (8 часов):

  1. Создать простое WinAPI-приложение с обработкой сообщений
  2. Написать GTK-приложение с кнопкой и обработкой события
  3. Исследовать структуру X11-приложения с помощью xev, xprop
  4. Создать простое Qt-приложение
Механизм сообщений в операционных системах
Системное программирование

Рекомендуемая литература

Основная:

  1. Таненбаум, Э. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2021.
  2. Kerrisk, M. The Linux Programming Interface. — No Starch Press, 2010.

Дополнительная:

  1. MSDN: Window Procedures, Message Queues
  2. GTK Documentation: https://docs.gtk.org/
  3. Qt Documentation: https://doc.qt.io/
  4. Wayland Protocol: https://wayland.freedesktop.org/docs/
  5. Xlib Programming Manual (Nye, A.)
Механизм сообщений в операционных системах

Напомнить, что эта лекция — переход от консольного программирования к событийно-ориентированному. Спросить студентов, кто работал с GUI-программированием раньше.

Быстро пробежать по плану. Обратить внимание, что первая половина — Windows (WinAPI), вторая — Linux. Основной фокус на концепциях, а не на синтаксисе конкретного API.

Это студентам знакомо — всё, что они писали до этого. Подчеркнуть: программа сама управляет потоком выполнения, ждёт ввода пользователя. Задать вопрос: «Что произойдёт, если пользователь введёт букву вместо числа?»

Ключевое отличие от консоли: программа не управляет потоком, а реагирует на события. Показать архитектуру слоями — каждый слой абстрагирует нижний. Спросить: «Может ли приложение напрямую писать в видеопамять?»

Обратить внимание на строку «Автоматизация»: консольные программы проще скриптить (pipes, redirects). GUI-автоматизация требует специальных инструментов (Selenium, AutoHotkey). Задать вопрос: «Когда лучше выбрать консольное приложение?»

Важная аналогия: класс окна в Windows — как класс в ООП. Определяет общие свойства, а конкретные окна — экземпляры. Подчеркнуть, что WindowProc — это «метод» класса, общий для всех окон этого класса. Частая ошибка: забыть обнулить структуру {0}.

Элементы управления в WinAPI — это тоже окна! CreateWindow создаёт и кнопку, и поле ввода. HMENU для идентификатора — нетипичное использование, может запутать. Показать, как дочерние окна отправляют WM_COMMAND родителю.

GWLP_USERDATA — распространённый паттерн для хранения указателя на C++ объект (this) в WinAPI. После изменения стиля обязательно нужен SetWindowPos с SWP_FRAMECHANGED, иначе изменения не отобразятся — типичная ошибка студентов. Подчеркнуть разницу: SetWindowLong — данные класса, SetWindowLongPtr — для 64-битной совместимости.

Ключевой переход мышления: от «программа говорит пользователю что делать» к «пользователь говорит программе что делать». Аналогия с рестораном: официант (диспетчер) принимает заказы (события) и передаёт на кухню (обработчик). Спросить: «Какая структура данных подходит для очереди сообщений?»

Три функции в цикле — три разных роли: GetMessage (получить), TranslateMessage (преобразовать), DispatchMessage (отправить). TranslateMessage превращает WM_KEYDOWN в WM_CHAR — спросить, зачем это нужно. PeekMessage — для игр и анимации, где нельзя блокировать. Частая ошибка: забыть проверить WM_QUIT при использовании PeekMessage.

Показать скелет WinAPI-приложения — это «Hello World» для Windows. Пять шагов всегда одни и те же. Задать вопрос: «Что будет, если убрать ShowWindow?» — окно создастся, но останется невидимым. Подчеркнуть: CreateWindowEx возвращает HWND, а не создаёт визуально — для этого нужен ShowWindow.

Оконная процедура вызывается ОС, а не программой — это callback. DefWindowProc обрабатывает всё, что мы не обработали — без него окно не будет работать. WM_DESTROY → PostQuitMessage — стандартный паттерн завершения. Типичная ошибка студентов: return 0 вместо DefWindowProc в default, из-за чего окно не двигается и не ресайзится.

wParam и lParam — «универсальные» параметры, смысл которых зависит от конкретного сообщения. Это историческое ограничение: в 16-битной Windows wParam был 16 бит, lParam — 32. Важно: MSG содержит поля time и pt — спросить, зачем они нужны (для отладки и логирования).

Спросить: «Может ли одно приложение отправить сообщение в окно другого?» — да, и это основа многих техник (в т.ч. вредоносных). Подчеркнуть три уровня: железо → ОС → приложение. Упомянуть, что таймер (WM_TIMER) — тоже источник сообщений, хотя его никто не нажимает.

Ключевое отличие: PostMessage кладёт в очередь и сразу возвращается, SendMessage дожидается обработки. SendMessage может вызвать дедлок, если два окна отправляют друг другу сообщения. Если hWnd = NULL в PostMessage, сообщение получают все окна с одинаковым ID (broadcast).

Распространённое заблуждение: «у каждого окна своя очередь». На самом деле очередь привязана к потоку, создавшему окно. Поэтому GUI-поток не должен блокироваться — иначе перестанут обрабатываться все окна этого потока. Спросить: «Что будет, если GUI-поток сделать sleep(5)?»

GetMessage блокирует поток — приложение «спит» и не потребляет CPU. PeekMessage позволяет делать работу между обработкой событий (игры, анимация). Фильтрация по диапазону полезна для обработки только ввода с клавиатуры (WM_KEYFIRST..WM_KEYLAST).

WM_KEYDOWN даёт виртуальный код (независимый от раскладки), WM_CHAR — уже символ с учётом языка. TranslateMessage в цикле сообщений именно и создаёт WM_CHAR из WM_KEYDOWN. Спросить: «Как определить, нажат ли Shift?» — через GetKeyState в обработчике WM_KEYDOWN.

Координаты мыши в lParam — LOWORD и HIWORD, но для корректной работы с 64-битными системами нужно использовать макросы GET_X_LPARAM и GET_Y_LPARAM. Частая ошибка: прямое использование (short)lParam. WM_MOUSEMOVE генерируется часто — рекомендовать использовать флаг отслеживания для оптимизации отрисовки.

WM_COMMAND — универсальный обработчик для меню, кнопок и акселераторов. Все они приходят в одно место. LOWORD(wParam) — идентификатор элемента. Для более сложных элементов (ListView, TreeView) используется WM_NOTIFY. Спросить: «Как отличить, от кнопки или от меню пришёл WM_COMMAND?» — через HIWORD(wParam).

Это большой слайд — выделить на него больше времени (5-7 мин). Сравнить с WinAPI: в Linux нет единого API, есть много слоёв. Показать параллель: WinAPI-сообщения ≈ X11-события ≈ GTK-signals ≈ Qt-signals/slots. Упомянуть, что в реальной работе на Linux почти никто не пишет на Xlib напрямую — используют GTK или Qt.

Пробежать по пунктам, попросить студентов назвать по одному ключевому слову к каждому пункту. Убедиться, что концепция «сообщения» как универсального механизма коммуникации в GUI понятна — она общая для Windows и Linux.

Рекомендовать студентам ответить на вопросы устно в парах перед следующей лекцией. Вопросы 5 и 7 — самые важные для понимания. Можно дать 2-3 минуты на обсуждение в парах прямо на лекции.

Задания рассчитаны на 8 часов самостоятельной работы. Рекомендовать начать с GTK (задание 2) — оно проще для компиляции на Linux. Задание 3 с xev/xprop можно выполнить прямо в терминале без написания кода — хороший способ познакомиться с X11-сообщениями визуально. Для задания 4 можно использовать Qt Creator.